﻿using System.Collections.Generic;
using System;
using System.IO;

using System.Linq;
using System.Windows.Forms;

namespace Microsoft.WowAddonStudio.TocEditor
{
    /// <summary>
    /// Class that represents a TOC (table of contents) document.
    /// Currently project properties are stored in a TocDocument
    /// object.
    /// </summary>
    public class TocDocument
    {
		/// <summary>
		/// Occurs when [document file changed].
		/// </summary>
		public event EventHandler DocumentFileChanged;

        #region Private members

    	private readonly IList<string> otherLines;
    	private readonly FileSystemWatcher watcher;
    	private string tocFilePath;

        #endregion

        #region Lifecycle methods

		/// <summary>
		/// Initializes a new instance of the <see cref="TocDocument"/> class.
		/// </summary>
        public TocDocument()
        {
            Files = new List<string>();
            Tags=new TocMetadata();
			otherLines=new List<string>();
        	watcher = new FileSystemWatcher
        	          	{
        	          		NotifyFilter = NotifyFilters.Attributes
						};
        }

		/// <summary>
		/// Handles the Changed event of the watcher control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="System.IO.FileSystemEventArgs"/> instance containing the event data.</param>
    	private void watcher_Changed(object sender, FileSystemEventArgs e)
		{
			if(e.FullPath==tocFilePath && DocumentFileChanged!=null)
			{
				Read(tocFilePath);
				DocumentFileChanged(this, new EventArgs());
			}
		}

        #endregion

        #region Public member functions and properties

        /// <summary>
        /// Reads the content of the TOC document from a specified location.
        /// The file has to be a correctly formatted TOC file, otherwise an
        /// application exception will be thrown.
        /// </summary>
        /// <param name="path">Path from where the content of the TOC document
        /// should be read.</param>
        public void Read(string path)
        {
            Clear();

        	TocFileFullName = path;

        	try
        	{
				using (StreamReader sr = File.OpenText(path))
				{
					while (!sr.EndOfStream)
					{
						string line = sr.ReadLine();

						if (IsValidMetadataLine(line))
							LoadMetadata(line);
						else if (IsValidFileEntryLine(line))
							LoadFile(line);
						else
							//Keep that line for user...
							otherLines.Add(line);
					}

					InitializeFileWatcher(path);
				}
        	}
        	catch (IOException ex)
        	{
        		MessageBox.Show(
        			String.Format("Reading the toc document failed due to: {0}. Please note that the property editor may not be up-to-date.", ex.Message),
					"Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
        	}
        }

    	/// <summary>
        /// Saves the content of the TOC document to a specified location.
        /// </summary>
        /// <param name="path">Path to where the content of the TOC document
        /// should be saved.</param>
        public void Write(string path)
        {
			watcher.Changed -= watcher_Changed;

            using (StreamWriter sw=File.CreateText(path))
            {
            	WriteComments(sw);
                WriteMetadata(sw);
                WriteIncludedFiles(sw);
            }

			watcher.Changed += watcher_Changed;
        }

    	/// <summary>
    	/// Retrieves the list of files in the TOC document.
    	/// </summary>
    	public IList<string> Files { get; private set; }

    	/// <summary>
    	/// Retrieves metadata from the TOC file.
    	/// </summary>
    	public TocMetadata Tags { get; private set; }

    	/// <summary>
		/// Gets or sets the full name of the toc file.
		/// </summary>
		/// <value>The full name of the toc file.</value>
		public string TocFileFullName { get; set; }

        #endregion

        #region Private member functions

		/// <summary>
		/// Initializes the file watcher.
		/// </summary>
		/// <param name="path">The path of the toc file to monitor.</param>
		private void InitializeFileWatcher(string path)
		{
			if (tocFilePath != path)
			{
				watcher.Changed -= watcher_Changed;
				tocFilePath = path;
				watcher.Path = Path.GetDirectoryName(tocFilePath);
				watcher.Filter = Path.GetFileName(tocFilePath);
				watcher.EnableRaisingEvents = true;
				watcher.Changed += watcher_Changed;
			}
		}

		/// <summary>
		/// Writes the included files.
		/// </summary>
		/// <param name="sw">The file to write to.</param>
        private void WriteIncludedFiles(TextWriter sw)
        {
            foreach (var fileRelativePath in Files)
            {
                sw.WriteLine(fileRelativePath);
            }
        }

		/// <summary>
		/// Writes valid metadata lines.
		/// </summary>
		/// <param name="sw">The file to write to.</param>
        private void WriteMetadata(TextWriter sw)
        {
            foreach (var tag in Tags.GetTags().OrderBy(kvp=>kvp.Key))
            {
                WriteFormattedMetadataLine(sw, tag.Key, tag.Value);
            }
        }

		/// <summary>
		/// Writes the comments or unrecognized lines.
		/// </summary>
		/// <param name="sw">The file to write to.</param>
		private void WriteComments(TextWriter sw)
		{
			foreach (var otherLine in otherLines)
			{
				if(!string.IsNullOrEmpty(otherLine))
					sw.WriteLine(otherLine);
			}
		}


		/// <summary>
		/// Writes the formatted metadata line.
		/// </summary>
		/// <param name="sw">The file to write to.</param>
		/// <param name="tagName">Name of the tag.</param>
		/// <param name="tagValue">The tag value.</param>
        private static void WriteFormattedMetadataLine(TextWriter sw, string tagName, string tagValue)
        {
            string formattedLine = string.Format("## {0}: {1}", tagName, tagValue);
			sw.WriteLine(formattedLine);
        }


		/// <summary>
		/// Clears all metadata from memory.
		/// </summary>
        private void Clear()
        {
            Files.Clear();
            Tags.Clear();
			otherLines.Clear();
        }


		/// <summary>
		/// Determines whether the specified line is a valid file entry line.
		/// </summary>
		/// <param name="line">The line.</param>
		/// <returns>
		/// 	<c>true</c> if the specified line is a valid file entry line; otherwise, <c>false</c>.
		/// </returns>
        private static bool IsValidFileEntryLine(string line)
        {
            try
            {
                return !string.IsNullOrEmpty(Path.GetExtension(line));
            }
            catch (ArgumentException)
            {
                return false;
            }
        }

		/// <summary>
		/// Determines whether the specified line is a valid metadata entry line.
		/// </summary>
		/// <param name="line">The line.</param>
		/// <returns>
		/// 	<c>true</c> if the specified line is a valid metadata entry line; otherwise, <c>false</c>.
		/// </returns>
        private static bool IsValidMetadataLine(string line)
        {
            bool result = false;

            if (!string.IsNullOrEmpty(line))
            {
                string trimmedLine = line.Trim();

                if (trimmedLine.StartsWith("##") && trimmedLine.IndexOf(':') > -1)
                {
                    int firstIndexOfColon = line.IndexOf(':');

                    string tagName = line.Substring(2, firstIndexOfColon - 2).Trim();

                	result = !string.IsNullOrEmpty(tagName) && firstIndexOfColon != line.Length - 1;
                }
            }

            return result;
        }

		/// <summary>
		/// Loads a file line.
		/// </summary>
		/// <param name="line">The line to load.</param>
        private void LoadFile(string line)
        {
            Files.Add(line);
        }

		/// <summary>
		/// Loads a metadata line.
		/// </summary>
		/// <param name="line">The line to load.</param>
        private void LoadMetadata(string line)
        {
            string tagName = ExtractTagName(line);
            string tagValue = ExtractTagValue(line);

            Tags[tagName] = tagValue;
        }

		/// <summary>
		/// Extracts the tag value from a line.
		/// </summary>
		/// <param name="line">The line to process.</param>
		/// <returns>Value of the tag</returns>
        private static string ExtractTagValue(string line)
        {
            string result = string.Empty;
            int indexOfColon = line.IndexOf(':');

            if (indexOfColon>-1)
                result = line.Substring(indexOfColon+1).Trim();

            return result;
        }

		/// <summary>
		/// Extracts the name of the tag.
		/// </summary>
		/// <param name="line">The line to process.</param>
		/// <returns>The name of the tag.</returns>
        private static string ExtractTagName(string line)
        {
			string trimmedLine = line.Trim();

			int firstIndexOfColon = trimmedLine.IndexOf(':');
            string result = trimmedLine.Substring(2, firstIndexOfColon-2).Trim();

            return result;
        }

        #endregion
    }
}